/*
** $Id: lobject.c $
** Some generic functions over Lua objects
** See Copyright Notice in lua.h
*/

#define lobject_c
#define LUA_CORE

#include "lprefix.h"

#include <locale.h>
#include <math.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "lua.h"

#include "lctype.h"
#include "ldebug.h"
#include "ldo.h"
#include "lmem.h"
#include "lobject.h"
#include "lstate.h"
#include "lstring.h"
#include "lvm.h"

/*
** Computes ceil(log2(x))
*/
int luaO_ceillog2(unsigned int x)
{
    static const lu_byte log_2[256] = { /* log_2[i] = ceil(log2(i - 1)) */
                                        0, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
                                        6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
                                        7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
                                        7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
                                        8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
                                        8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
                                        8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
                                        8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8
    };
    int l = 0;
    x--;
    while (x >= 256) {
        l += 8;
        x >>= 8;
    }
    return l + log_2[x];
}

static lua_Integer intarith(lua_State *L, int op, lua_Integer v1,
                            lua_Integer v2)
{
    switch (op) {
        case LUA_OPADD:
            return intop(+, v1, v2);
        case LUA_OPSUB:
            return intop(-, v1, v2);
        case LUA_OPMUL:
            return intop(*, v1, v2);
        case LUA_OPMOD:
            return luaV_mod(L, v1, v2);
        case LUA_OPIDIV:
            return luaV_idiv(L, v1, v2);
        case LUA_OPBAND:
            return intop(&, v1, v2);
        case LUA_OPBOR:
            return intop(|, v1, v2);
        case LUA_OPBXOR:
            return intop(^, v1, v2);
        case LUA_OPSHL:
            return luaV_shiftl(v1, v2);
        case LUA_OPSHR:
            return luaV_shiftl(v1, -v2);
        case LUA_OPUNM:
            return intop(-, 0, v1);
        case LUA_OPBNOT:
            return intop(^, ~l_castS2U(0), v1);
        default:
            lua_assert(0);
            return 0;
    }
}

static lua_Number numarith(lua_State *L, int op, lua_Number v1,
                           lua_Number v2)
{
    switch (op) {
        case LUA_OPADD:
            return luai_numadd(L, v1, v2);
        case LUA_OPSUB:
            return luai_numsub(L, v1, v2);
        case LUA_OPMUL:
            return luai_nummul(L, v1, v2);
        case LUA_OPDIV:
            return luai_numdiv(L, v1, v2);
        case LUA_OPPOW:
            return luai_numpow(L, v1, v2);
        case LUA_OPIDIV:
            return luai_numidiv(L, v1, v2);
        case LUA_OPUNM:
            return luai_numunm(L, v1);
        case LUA_OPMOD:
            return luaV_modf(L, v1, v2);
        default:
            lua_assert(0);
            return 0;
    }
}

int luaO_rawarith(lua_State *L, int op, const TValue *p1, const TValue *p2,
                  TValue *res)
{
    switch (op) {
        case LUA_OPBAND:
        case LUA_OPBOR:
        case LUA_OPBXOR:
        case LUA_OPSHL:
        case LUA_OPSHR:
        case LUA_OPBNOT: { /* operate only on integers */
            lua_Integer i1;
            lua_Integer i2;
            if (tointegerns(p1, &i1) && tointegerns(p2, &i2)) {
                setivalue(res, intarith(L, op, i1, i2));
                return 1;
            } else
                return 0; /* fail */
        }
        case LUA_OPDIV:
        case LUA_OPPOW: { /* operate only on floats */
            lua_Number n1;
            lua_Number n2;
            if (tonumberns(p1, n1) && tonumberns(p2, n2)) {
                setfltvalue(res, numarith(L, op, n1, n2));
                return 1;
            } else
                return 0; /* fail */
        }
        default: { /* other operations */
            lua_Number n1;
            lua_Number n2;
            if (ttisinteger(p1) && ttisinteger(p2)) {
                setivalue(res, intarith(L, op, ivalue(p1), ivalue(p2)));
                return 1;
            } else if (tonumberns(p1, n1) && tonumberns(p2, n2)) {
                setfltvalue(res, numarith(L, op, n1, n2));
                return 1;
            } else
                return 0; /* fail */
        }
    }
}

void luaO_arith(lua_State *L, int op, const TValue *p1, const TValue *p2,
                StkId res)
{
    if (!luaO_rawarith(L, op, p1, p2, s2v(res))) {
        /* could not perform raw operation; try metamethod */
        luaT_trybinTM(L, p1, p2, res, cast(TMS, (op - LUA_OPADD) + TM_ADD));
    }
}

int luaO_hexavalue(int c)
{
    if (lisdigit(c))
        return c - '0';
    else
        return (ltolower(c) - 'a') + 10;
}

static int isneg(const char **s)
{
    if (**s == '-') {
        (*s)++;
        return 1;
    } else if (**s == '+')
        (*s)++;
    return 0;
}

/*
** {==================================================================
** Lua's implementation for 'lua_strx2number'
** ===================================================================
*/

#if !defined(lua_strx2number)

/* maximum number of significant digits to read (to avoid overflows
   even with single floats) */
#define MAXSIGDIG 30

/*
** convert a hexadecimal numeric string to a number, following
** C99 specification for 'strtod'
*/
static lua_Number lua_strx2number(const char *s, char **endptr)
{
    int dot = lua_getlocaledecpoint();
    lua_Number r = l_mathop(0.0); /* result (accumulator) */
    int sigdig = 0;               /* number of significant digits */
    int nosigdig = 0;             /* number of non-significant digits */
    int e = 0;                    /* exponent correction */
    int neg;                      /* 1 if number is negative */
    int hasdot = 0;               /* true after seen a dot */
    *endptr = cast_charp(s);      /* nothing is valid yet */
    while (lisspace(cast_uchar(*s)))
        s++;                                                  /* skip initial spaces */
    neg = isneg(&s);                                          /* check sign */
    if (!(*s == '0' && (*(s + 1) == 'x' || *(s + 1) == 'X'))) /* check '0x' */
        return l_mathop(0.0);                                 /* invalid format (no '0x') */
    for (s += 2;; s++) {                                      /* skip '0x' and read numeral */
        if (*s == dot) {
            if (hasdot)
                break; /* second dot? stop loop */
            else
                hasdot = 1;
        } else if (lisxdigit(cast_uchar(*s))) {
            if (sigdig == 0 && *s == '0') /* non-significant digit (zero)? */
                nosigdig++;
            else if (++sigdig <= MAXSIGDIG) /* can read it without overflow? */
                r = (r * l_mathop(16.0)) + luaO_hexavalue(*s);
            else
                e++; /* too many digits; ignore, but still count for exponent */
            if (hasdot)
                e--; /* decimal digit? correct exponent */
        } else
            break; /* neither a dot nor a digit */
    }
    if (nosigdig + sigdig == 0)   /* no digits? */
        return l_mathop(0.0);     /* invalid format */
    *endptr = cast_charp(s);      /* valid up to here */
    e *= 4;                       /* each digit multiplies/divides value by 2^4 */
    if (*s == 'p' || *s == 'P') { /* exponent part? */
        int exp1 = 0;             /* exponent value */
        int neg1;                 /* exponent sign */
        s++;                      /* skip 'p' */
        neg1 = isneg(&s);         /* sign */
        if (!lisdigit(cast_uchar(*s)))
            return l_mathop(0.0);        /* invalid; must have at least one digit */
        while (lisdigit(cast_uchar(*s))) /* read exponent */
            exp1 = exp1 * 10 + *(s++) - '0';
        if (neg1)
            exp1 = -exp1;
        e += exp1;
        *endptr = cast_charp(s); /* valid up to here */
    }
    if (neg)
        r = -r;
    return l_mathop(ldexp)(r, e);
}

#endif
/* }====================================================== */

/* maximum length of a numeral to be converted to a number */
#if !defined(L_MAXLENNUM)
#define L_MAXLENNUM 200
#endif

/*
** Convert string 's' to a Lua number (put in 'result'). Return NULL on
** fail or the address of the ending '\0' on success. ('mode' == 'x')
** means a hexadecimal numeral.
*/
static const char *l_str2dloc(const char *s, lua_Number *result, int mode)
{
    char *endptr;
    *result = (mode == 'x') ? lua_strx2number(s, &endptr) /* try to convert */
                              :
                              lua_str2number(s, &endptr);
    if (endptr == s)
        return NULL; /* nothing recognized? */
    while (lisspace(cast_uchar(*endptr)))
        endptr++;                             /* skip trailing spaces */
    return (*endptr == '\0') ? endptr : NULL; /* OK iff no trailing chars */
}

/*
** Convert string 's' to a Lua number (put in 'result') handling the
** current locale.
** This function accepts both the current locale or a dot as the radix
** mark. If the conversion fails, it may mean number has a dot but
** locale accepts something else. In that case, the code copies 's'
** to a buffer (because 's' is read-only), changes the dot to the
** current locale radix mark, and tries to convert again.
** The variable 'mode' checks for special characters in the string:
** - 'n' means 'inf' or 'nan' (which should be rejected)
** - 'x' means a hexadecimal numeral
** - '.' just optimizes the search for the common case (no special chars)
*/
static const char *l_str2d(const char *s, lua_Number *result)
{
    const char *endptr;
    const char *pmode = luaport_strpbrk(s, ".xXnN"); /* look for special chars */
    int mode = pmode ? ltolower(cast_uchar(*pmode)) : 0;
    if (mode == 'n') /* reject 'inf' and 'nan' */
        return NULL;
    endptr = l_str2dloc(s, result, mode); /* try to convert */
    if (endptr == NULL) {                 /* failed? may be a different locale */
        char buff[L_MAXLENNUM + 1];
        const char *pdot = luaport_strchr(s, '.');
        if (pdot == NULL || luaport_strlen(s) > L_MAXLENNUM)
            return NULL;                          /* string too long or no dot; fail */
        luaport_strcpy(buff, s);                  /* copy string to buffer */
        buff[pdot - s] = lua_getlocaledecpoint(); /* correct decimal point */
        endptr = l_str2dloc(buff, result, mode);  /* try again */
        if (endptr != NULL)
            endptr = s + (endptr - buff); /* make relative to 's' */
    }
    return endptr;
}

#define MAXBY10  cast(lua_Unsigned, LUA_MAXINTEGER / 10)
#define MAXLASTD cast_int(LUA_MAXINTEGER % 10)

static const char *l_str2int(const char *s, lua_Integer *result)
{
    lua_Unsigned a = 0;
    int empty = 1;
    int neg;
    while (lisspace(cast_uchar(*s)))
        s++; /* skip initial spaces */
    neg = isneg(&s);
    if (s[0] == '0' &&
        (s[1] == 'x' || s[1] == 'X')) { /* hex? */
        s += 2;                         /* skip '0x' */
        for (; lisxdigit(cast_uchar(*s)); s++) {
            a = a * 16 + luaO_hexavalue(*s);
            empty = 0;
        }
    } else { /* decimal */
        for (; lisdigit(cast_uchar(*s)); s++) {
            int d = *s - '0';
            if (a >= MAXBY10 && (a > MAXBY10 || d > MAXLASTD + neg)) /* overflow? */
                return NULL;                                         /* do not accept it (as integer) */
            a = a * 10 + d;
            empty = 0;
        }
    }
    while (lisspace(cast_uchar(*s)))
        s++; /* skip trailing spaces */
    if (empty || *s != '\0')
        return NULL; /* something wrong in the numeral */
    else {
        *result = l_castU2S((neg) ? 0u - a : a);
        return s;
    }
}

size_t luaO_str2num(const char *s, TValue *o)
{
    lua_Integer i;
    lua_Number n;
    const char *e;
    if ((e = l_str2int(s, &i)) != NULL) { /* try as an integer */
        setivalue(o, i);
    } else if ((e = l_str2d(s, &n)) != NULL) { /* else try as a float */
        setfltvalue(o, n);
    } else
        return 0;       /* conversion failed */
    return (e - s) + 1; /* success; return string size */
}

int luaO_utf8esc(char *buff, unsigned long x)
{
    int n = 1; /* number of bytes put in buffer (backwards) */
    lua_assert(x <= 0x7FFFFFFFu);
    if (x < 0x80) /* ascii? */
        buff[UTF8BUFFSZ - 1] = cast_char(x);
    else {                       /* need continuation bytes */
        unsigned int mfb = 0x3f; /* maximum that fits in first byte */
        do {                     /* add continuation bytes */
            buff[UTF8BUFFSZ - (n++)] = cast_char(0x80 | (x & 0x3f));
            x >>= 6;                                       /* remove added bits */
            mfb >>= 1;                                     /* now there is one less bit available in first byte */
        } while (x > mfb);                                 /* still needs continuation byte? */
        buff[UTF8BUFFSZ - n] = cast_char((~mfb << 1) | x); /* add first byte */
    }
    return n;
}

/*
** Maximum length of the conversion of a number to a string. Must be
** enough to accommodate both LUA_INTEGER_FMT and LUA_NUMBER_FMT.
** (For a long long int, this is 19 digits plus a sign and a final '\0',
** adding to 21. For a long double, it can go to a sign, 33 digits,
** the dot, an exponent letter, an exponent sign, 5 exponent digits,
** and a final '\0', adding to 43.)
*/
#define MAXNUMBER2STR 44

/*
** Convert a number object to a string, adding it to a buffer
*/
static int tostringbuff(TValue *obj, char *buff)
{
    int len;
    lua_assert(ttisnumber(obj));
    if (ttisinteger(obj))
        len = lua_integer2str(buff, MAXNUMBER2STR, ivalue(obj));
    else {
        len = lua_number2str(buff, MAXNUMBER2STR, fltvalue(obj));
        if (buff[luaport_strspn(buff, "-0123456789")] == '\0') { /* looks like an int? */
            buff[len++] = lua_getlocaledecpoint();
            buff[len++] = '0'; /* adds '.0' to result */
        }
    }
    return len;
}

/*
** Convert a number object to a Lua string, replacing the value at 'obj'
*/
void luaO_tostring(lua_State *L, TValue *obj)
{
    char buff[MAXNUMBER2STR];
    int len = tostringbuff(obj, buff);
    setsvalue(L, obj, luaS_newlstr(L, buff, len));
}

/*
** {==================================================================
** 'luaO_pushvfstring'
** ===================================================================
*/

/* size for buffer space used by 'luaO_pushvfstring' */
#define BUFVFS 200

/* buffer used by 'luaO_pushvfstring' */
typedef struct BuffFS {
    lua_State *L;
    int pushed;         /* number of string pieces already on the stack */
    int blen;           /* length of partial string in 'space' */
    char space[BUFVFS]; /* holds last part of the result */
} BuffFS;

/*
** Push given string to the stack, as part of the buffer, and
** join the partial strings in the stack into one.
*/
static void pushstr(BuffFS *buff, const char *str, size_t l)
{
    lua_State *L = buff->L;
    setsvalue2s(L, L->top, luaS_newlstr(L, str, l));
    L->top++; /* may use one extra slot */
    buff->pushed++;
    luaV_concat(L, buff->pushed); /* join partial results into one */
    buff->pushed = 1;
}

/*
** empty the buffer space into the stack
*/
static void clearbuff(BuffFS *buff)
{
    pushstr(buff, buff->space, buff->blen); /* push buffer contents */
    buff->blen = 0;                         /* space now is empty */
}

/*
** Get a space of size 'sz' in the buffer. If buffer has not enough
** space, empty it. 'sz' must fit in an empty buffer.
*/
static char *getbuff(BuffFS *buff, int sz)
{
    lua_assert(buff->blen <= BUFVFS);
    lua_assert(sz <= BUFVFS);
    if (sz > BUFVFS - buff->blen) /* not enough space? */
        clearbuff(buff);
    return buff->space + buff->blen;
}

#define addsize(b, sz) ((b)->blen += (sz))

/*
** Add 'str' to the buffer. If string is larger than the buffer space,
** push the string directly to the stack.
*/
static void addstr2buff(BuffFS *buff, const char *str, size_t slen)
{
    if (slen <= BUFVFS) { /* does string fit into buffer? */
        char *bf = getbuff(buff, cast_int(slen));
        luaport_memcpy(bf, str, slen); /* add string to buffer */
        addsize(buff, cast_int(slen));
    } else {                      /* string larger than buffer */
        clearbuff(buff);          /* string comes after buffer's content */
        pushstr(buff, str, slen); /* push string */
    }
}

/*
** Add a number to the buffer.
*/
static void addnum2buff(BuffFS *buff, TValue *num)
{
    char *numbuff = getbuff(buff, MAXNUMBER2STR);
    int len = tostringbuff(num, numbuff); /* format number into 'numbuff' */
    addsize(buff, len);
}

/*
** this function handles only '%d', '%c', '%f', '%p', '%s', and '%%'
   conventional formats, plus Lua-specific '%I' and '%U'
*/
const char *luaO_pushvfstring(lua_State *L, const char *fmt, va_list argp)
{
    BuffFS buff;   /* holds last part of the result */
    const char *e; /* points to next '%' */
    buff.pushed = buff.blen = 0;
    buff.L = L;
    while ((e = luaport_strchr(fmt, '%')) != NULL) {
        addstr2buff(&buff, fmt, e - fmt); /* add 'fmt' up to '%' */
        switch (*(e + 1)) {               /* conversion specifier */
            case 's': {                   /* zero-terminated string */
                const char *s = va_arg(argp, char *);
                if (s == NULL)
                    s = "(null)";
                addstr2buff(&buff, s, luaport_strlen(s));
                break;
            }
            case 'c': { /* an 'int' as a character */
                char c = cast_uchar(va_arg(argp, int));
                addstr2buff(&buff, &c, sizeof(char));
                break;
            }
            case 'd': { /* an 'int' */
                TValue num;
                setivalue(&num, va_arg(argp, int));
                addnum2buff(&buff, &num);
                break;
            }
            case 'I': { /* a 'lua_Integer' */
                TValue num;
                setivalue(&num, cast(lua_Integer, va_arg(argp, l_uacInt)));
                addnum2buff(&buff, &num);
                break;
            }
            case 'f': { /* a 'lua_Number' */
                TValue num;
                setfltvalue(&num, cast_num(va_arg(argp, l_uacNumber)));
                addnum2buff(&buff, &num);
                break;
            }
            case 'p': {                                /* a pointer */
                const int sz = 3 * sizeof(void *) + 8; /* enough space for '%p' */
                char *bf = getbuff(&buff, sz);
                void *p = va_arg(argp, void *);
                int len = lua_pointer2str(bf, sz, p);
                addsize(&buff, len);
                break;
            }
            case 'U': { /* a 'long' as a UTF-8 sequence */
                char bf[UTF8BUFFSZ];
                int len = luaO_utf8esc(bf, va_arg(argp, long));
                addstr2buff(&buff, bf + UTF8BUFFSZ - len, len);
                break;
            }
            case '%': {
                addstr2buff(&buff, "%", 1);
                break;
            }
            default: {
                luaG_runerror(L, "invalid option '%%%c' to 'lua_pushfstring'",
                              *(e + 1));
            }
        }
        fmt = e + 2; /* skip '%' and the specifier */
    }
    addstr2buff(&buff, fmt, luaport_strlen(fmt)); /* rest of 'fmt' */
    clearbuff(&buff);                             /* empty buffer into the stack */
    lua_assert(buff.pushed == 1);
    return svalue(s2v(L->top - 1));
}

const char *luaO_pushfstring(lua_State *L, const char *fmt, ...)
{
    const char *msg;
    va_list argp;
    va_start(argp, fmt);
    msg = luaO_pushvfstring(L, fmt, argp);
    va_end(argp);
    return msg;
}

/* }================================================================== */

#define RETS            "..."
#define PRE             "[string \""
#define POS             "\"]"

#define addstr(a, b, l) (luaport_memcpy(a, b, (l) * sizeof(char)), a += (l))

void luaO_chunkid(char *out, const char *source, size_t srclen)
{
    size_t bufflen = LUA_IDSIZE; /* free space in buffer */
    if (*source == '=') {        /* 'literal' source */
        if (srclen <= bufflen)   /* small enough? */
            luaport_memcpy(out, source + 1, srclen * sizeof(char));
        else { /* truncate it */
            addstr(out, source + 1, bufflen - 1);
            *out = '\0';
        }
    } else if (*source == '@') { /* file name */
        if (srclen <= bufflen)   /* small enough? */
            luaport_memcpy(out, source + 1, srclen * sizeof(char));
        else { /* add '...' before rest of name */
            addstr(out, RETS, LL(RETS));
            bufflen -= LL(RETS);
            luaport_memcpy(out, source + 1 + srclen - bufflen, bufflen * sizeof(char));
        }
    } else {                                           /* string; format as [string "source"] */
        const char *nl = luaport_strchr(source, '\n'); /* find first new line (if any) */
        addstr(out, PRE, LL(PRE));                     /* add prefix */
        bufflen -= LL(PRE RETS POS) + 1;               /* save space for prefix+suffix+'\0' */
        if (srclen < bufflen && nl == NULL) {          /* small one-line source? */
            addstr(out, source, srclen);               /* keep it */
        } else {
            if (nl != NULL)
                srclen = nl - source; /* stop at first newline */
            if (srclen > bufflen)
                srclen = bufflen;
            addstr(out, source, srclen);
            addstr(out, RETS, LL(RETS));
        }
        luaport_memcpy(out, POS, (LL(POS) + 1) * sizeof(char));
    }
}
